home *** CD-ROM | disk | FTP | other *** search
/ Tech Arsenal 1 / Tech Arsenal (Arsenal Computer).ISO / tek-13 / mewin10s.zip / SEARCH.C < prev    next >
C/C++ Source or Header  |  1992-03-11  |  43KB  |  1,804 lines

  1. /*
  2.  * The functions in this file implement commands that search in the forward
  3.  * and backward directions.
  4.  *
  5.  * (History comments formerly here have been moved to history.c)
  6.  *
  7.  */
  8.  
  9. #include <stdio.h>
  10. #include "estruct.h"
  11. #include "eproto.h"
  12. #include "edef.h"
  13. #include "elang.h"
  14.  
  15. static int    patlenadd;
  16. static int    lastchfjump, lastchbjump;
  17. static int    deltaf[HICHAR], deltab[HICHAR];
  18. static int    replen;        /* length of replacement string */
  19.  
  20. #if    MAGIC
  21. static int        group_count;
  22. static int    group_len[MAXGROUPS];
  23. static REGION    group_reg[MAXGROUPS];
  24. #endif
  25.  
  26. /*
  27.  * forwsearch -- Search forward.  Get a search string from the user, and
  28.  *    search for the string.  If found, reset the "." to be just after
  29.  *    the match string, and (perhaps) repaint the display.
  30.  */
  31. int PASCAL NEAR forwsearch(f, n)
  32. int f, n;    /* default flag / numeric argument */
  33. {
  34.     register int    status;
  35.  
  36.     /* If n is negative, search backwards.
  37.      * Otherwise proceed by asking for the search string.
  38.      */
  39.     if (n < 0)
  40.         return (backsearch(f, -n));
  41.  
  42.     /* Ask the user for the text of a pattern.  If the response is
  43.      * TRUE (responses other than FALSE are possible), search for
  44.      * pattern for up to n times, as long as the pattern is there
  45.      * to be found.
  46.      */
  47.     if ((status = readpattern(TEXT78, &pat[0], TRUE)) == TRUE)
  48.         status = forwhunt(f, n);
  49. /*                "Search" */
  50.  
  51.     return (status);
  52. }
  53.  
  54. /*
  55.  * forwhunt -- Search forward for a previously acquired search string.
  56.  *    If found, reset the "." to be just after the match string,
  57.  *    and (perhaps) repaint the display.
  58.  */
  59. int PASCAL NEAR forwhunt(f, n)
  60. int f, n;    /* default flag / numeric argument */
  61. {
  62.     register int    spoint = PTEND;
  63.     register int    status;
  64.  
  65.     if (n < 0)        /* search backwards */
  66.         return (backhunt(f, -n));
  67.  
  68.     /* Make sure a pattern exists, or that we didn't switch
  69.      * into MAGIC mode after we entered the pattern.
  70.      */
  71.     if (pat[0] == '\0') {
  72.         mlwrite(TEXT80);
  73. /*            "No pattern set" */
  74.         return FALSE;
  75.     }
  76.  
  77. #if    MAGIC
  78.     if ((curwp->w_bufp->b_mode & MDMAGIC) && mcpat[0].mc_type == MCNIL) {
  79.         if (!mcstr())
  80.             return FALSE;
  81.     }
  82. #endif
  83.  
  84.     /*
  85.      * Do one extra search to get us past our current
  86.      * match, if the search type has us at the start
  87.      * of a match, instead of after a match.
  88.      */
  89.     if (searchtype == SRBEGIN) {
  90.         spoint = PTBEG;
  91.         if (lastflag & CFSRCH)
  92.             n = (n > 2)? (n + 1): 2;
  93.     }
  94.  
  95. #if    MAGIC
  96.     if (magical && (curwp->w_bufp->b_mode & MDMAGIC))
  97.         status = mcscanner(FORWARD, spoint, n);
  98.     else
  99. #endif
  100.         status = scanner(FORWARD, spoint, n);
  101.  
  102.     /* Complain if not there.
  103.      */
  104.     if (status == FALSE)
  105.         mlwrite(TEXT79);
  106. /*            "Not found" */
  107.  
  108.     thisflag |= CFSRCH;
  109.     return (status);
  110. }
  111.  
  112. /*
  113.  * backsearch -- Reverse search.  Get a search string from the user, and
  114.  *    search, starting at "." and proceeding toward the front of the buffer.
  115.  *    If found "." is left pointing at the first character of the pattern
  116.  *    (the last character that was matched).
  117.  */
  118. int PASCAL NEAR backsearch(f, n)
  119. int f, n;    /* default flag / numeric argument */
  120. {
  121.     register int    status;
  122.  
  123.     /* If n is negative, search forwards.
  124.      * Otherwise proceed by asking for the search string.
  125.      */
  126.     if (n < 0)
  127.         return (forwsearch(f, -n));
  128.  
  129.     /* Ask the user for the text of a pattern.  If the response is
  130.      * TRUE (responses other than FALSE are possible), search for
  131.      * pattern for up to n times, as long as the pattern is there
  132.      * to be found.
  133.      */
  134.     if ((status = readpattern(TEXT81, &pat[0], TRUE)) == TRUE)
  135.         status = backhunt(f, n);
  136. /*                "Reverse search" */
  137.  
  138.     return (status);
  139. }
  140.  
  141. /*
  142.  * backhunt -- Reverse search for a previously acquired search string,
  143.  *    starting at "." and proceeding toward the front of the buffer.
  144.  *    If found "." is left pointing at the first character of the pattern
  145.  *    (the last character that was matched).
  146.  */
  147. int PASCAL NEAR backhunt(f, n)
  148. int f, n;    /* default flag / numeric argument */
  149. {
  150.     register int    spoint = PTBEG;
  151.     register int    status;
  152.  
  153.     if (n < 0)
  154.         return (forwhunt(f, -n));
  155.  
  156.     /* Make sure a pattern exists, or that we didn't switch
  157.      * into MAGIC mode after we entered the pattern.
  158.      */
  159.     if (tap[0] == '\0') {
  160.         mlwrite(TEXT80);
  161. /*            "No pattern set" */
  162.         return FALSE;
  163.     }
  164. #if    MAGIC
  165.     if ((curwp->w_bufp->b_mode & MDMAGIC) && tapcm[0].mc_type == MCNIL) {
  166.         if (!mcstr())
  167.             return FALSE;
  168.     }
  169. #endif
  170.  
  171.     /*
  172.      * Do one extra search to get us past our current
  173.      * match, if the search type has us at the start
  174.      * of a match, instead of after a match.
  175.      */
  176.     if (searchtype == SREND) {
  177.         spoint = PTEND;
  178.         if (lastflag & CFSRCH)
  179.             n = (n > 2)? (n + 1): 2;
  180.     }
  181.  
  182. #if    MAGIC
  183.     if (magical && (curwp->w_bufp->b_mode & MDMAGIC))
  184.         status = mcscanner(REVERSE, spoint, n);
  185.     else
  186. #endif
  187.         status = scanner(REVERSE, spoint, n);
  188.  
  189.     /* Complain if not there.
  190.      */
  191.     if (status == FALSE)
  192.         mlwrite(TEXT79);
  193. /*            "Not found" */
  194.  
  195.     thisflag |= CFSRCH;
  196.     return (status);
  197. }
  198.  
  199. #if    MAGIC
  200. /*
  201.  * mcscanner -- Search for a meta-pattern in either direction.  If found,
  202.  *    reset the "." to be at the start or just after the match string,
  203.  *    and (perhaps) repaint the display.
  204.  */
  205. int PASCAL NEAR mcscanner(direct, beg_or_end, repeats)
  206. int    direct;        /* which way to go.*/
  207. int    beg_or_end;    /* put point at beginning or end of pattern.*/
  208. int    repeats;    /* search repetitions.*/
  209. {
  210.     MC    *mcpatrn;    /* pointer into pattern */
  211.     LINE    *curline;    /* current line during scan */
  212.     int    curoff;        /* position within current line */
  213.  
  214.     /* If we are going in reverse, then the 'end' is actually
  215.      * the beginning of the pattern.  Toggle it.
  216.      */
  217.     beg_or_end ^= direct;
  218.  
  219.     /* Another directional problem: if we are searching forwards,
  220.      * use the pattern in mcpat[].  Otherwise, use the reversed
  221.      * pattern in tapcm[].
  222.      */
  223.     mcpatrn = (direct == FORWARD)? &mcpat[0]: &tapcm[0];
  224.  
  225.     /* Setup local scan pointers to global ".".
  226.      */
  227.     curline = curwp->w_dotp;
  228.     curoff  = curwp->w_doto;
  229.  
  230.     /* Scan each character until we hit the head link record.
  231.      */
  232.     while (!boundry(curline, curoff, direct)) {
  233.         /* Save the current position in case we need to
  234.          * restore it on a match, and initialize matchlen to
  235.          * zero in case we are doing a search for replacement.
  236.          */
  237.         matchline = curline;
  238.         matchoff = curoff;
  239.         matchlen = 0;
  240.  
  241. #if     WINDOW_MSWIN
  242.         {
  243.             static int  o = 0;
  244.             if (--o < 0) {
  245.             longop(TRUE);
  246.             o = 100;   /* to lower overhead, only 1/100
  247.                        calls to longop */
  248.             }
  249.         }
  250. #endif
  251.         if (amatch(mcpatrn, direct, &curline, &curoff)) {
  252.             /* A SUCCESSFULL MATCH!!!
  253.              * Flag that we have moved,
  254.              * reset the global "." pointers.
  255.              */
  256.             curwp->w_flag |= WFMOVE;
  257.             if (beg_or_end == PTEND)    /* at end of string */
  258.             {
  259.                 curwp->w_dotp = curline;
  260.                 curwp->w_doto = curoff;
  261.             }
  262.             else        /* at beginning of string */
  263.             {
  264.                 curwp->w_dotp = matchline;
  265.                 curwp->w_doto = matchoff;
  266.             }
  267.  
  268.             /* If we're heading in reverse, set the "match"
  269.              * pointers to the start of the string, for savematch().
  270.              */
  271.             if (direct == REVERSE) {
  272.                 matchline = curline;
  273.                 matchoff = curoff;
  274.             }
  275.  
  276.             if (savematch() == ABORT)
  277.                 return ABORT;
  278.  
  279.             /* Continue scanning if we haven't found
  280.              * the nth match.
  281.              */
  282.             if (--repeats <= 0)
  283.                 return TRUE;
  284.         }
  285.  
  286.         /* Advance the cursor.
  287.          */
  288.         nextch(&curline, &curoff, direct);
  289.     }
  290.  
  291.     return FALSE;    /* We could not find a match.*/
  292. }
  293.  
  294. /*
  295.  * amatch -- Search for a meta-pattern in either direction.  Based on the
  296.  *    recursive routine amatch() (for "anchored match") in
  297.  *    Kernighan & Plauger's "Software Tools".
  298.  */
  299. int PASCAL NEAR    amatch(mcptr, direct, pcwline, pcwoff)
  300. register MC    *mcptr;        /* pattern to scan for */
  301. int        direct;        /* which way to go.*/
  302. LINE        **pcwline;    /* current line during scan */
  303. int        *pcwoff;    /* position within current line */
  304. {
  305.     register int    c;    /* character at current position */
  306.     LINE    *curline;    /* current line during scan */
  307.     int    curoff;        /* position within current line */
  308.     int    n_chars;    /* number of chars matched in a closure.*/
  309.     int    cl_min;        /* minimum number of chars matched in closure.*/
  310.     int    cl_type;    /* Which closure type?.*/
  311.  
  312.     /* Set up local scan pointers to ".".
  313.      */
  314.     curline = *pcwline;
  315.     curoff = *pcwoff;
  316.  
  317.     /*
  318.      * Loop through the meta-pattern, laughing all the way.
  319.      */
  320.     while (mcptr->mc_type != MCNIL) {
  321.         /* Is the current meta-character modified
  322.          * by a closure?
  323.          */
  324.         if (cl_type = (mcptr->mc_type & ALLCLOS)) {
  325.             c = nextch(&curline, &curoff, direct);
  326.             n_chars = 0;
  327.  
  328.             if (cl_type == ZEROONE) {
  329.                 cl_min = 0;
  330.  
  331.                 if (mceq(c, mcptr)) {
  332.                     c = nextch(&curline, &curoff, direct);
  333.                     n_chars++;
  334.                 }
  335.             }
  336.             else
  337.             {
  338.                 /* Minimum number of characters that may
  339.                  * match is 0 or 1.
  340.                  */
  341.                 cl_min = (cl_type == CLOSURE_1);
  342.  
  343.                 /* Match as many characters as possible
  344.                  * against the current meta-character.
  345.                  */
  346.                 while (mceq(c, mcptr)) {
  347.                     c = nextch(&curline, &curoff, direct);
  348.                     n_chars++;
  349.                 }
  350.             }
  351.  
  352.             /* We are now at the character that made us
  353.              * fail.  Try to match the rest of the pattern.
  354.              * Shrink the closure by one for each failure.
  355.              */
  356.             mcptr++;
  357.             matchlen += n_chars;
  358.             for (;;) {
  359.                 if (n_chars < cl_min) {
  360.                     matchlen -= n_chars;
  361.                     return FALSE;
  362.                 }
  363.  
  364.                 nextch(&curline, &curoff, direct ^ REVERSE);
  365.  
  366.                 if (amatch(mcptr, direct, &curline, &curoff))
  367.                     goto success;
  368.                 matchlen--;
  369.                 n_chars--;
  370.             }
  371.         }
  372.         else if (mcptr->mc_type == GRPBEG) {
  373.             group_reg[mcptr->u.group_no].r_offset = curoff;
  374.             group_reg[mcptr->u.group_no].r_linep = curline;
  375.             group_reg[mcptr->u.group_no].r_size = (direct == FORWARD)? -matchlen: matchlen;
  376.         }
  377.         else if (mcptr->mc_type == GRPEND) {
  378.             group_len[mcptr->u.group_no] = (direct == FORWARD)? matchlen: -matchlen;
  379.         }
  380.         else if (mcptr->mc_type == BOL) {
  381.             if (curoff != 0)
  382.                 return FALSE;
  383.         }
  384.         else if (mcptr->mc_type == EOL) {
  385.             if (curoff != llength(curline))
  386.                 return FALSE;
  387.         }
  388.         else
  389.         {
  390.             /* A character to compare against
  391.              * the meta-character equal function.
  392.              * If we still match, increment the length.
  393.              */
  394.             if (!mceq(nextch(&curline, &curoff, direct), mcptr))
  395.                 return FALSE;
  396.  
  397.             matchlen++;
  398.         }
  399.  
  400.         mcptr++;
  401.     }            /* End of mcptr loop.*/
  402.  
  403.     /* A SUCCESSFULL MATCH!!!
  404.      * Reset the "." pointers.
  405.      */
  406. success:
  407.     *pcwline = curline;
  408.     *pcwoff  = curoff;
  409.  
  410.     return (TRUE);
  411. }
  412. #endif
  413.  
  414. /*
  415.  * scanner -- Search for a pattern in either direction.  If found,
  416.  *    reset the "." to be at the start or just after the match string,
  417.  *    and (perhaps) repaint the display.
  418.  *    Fast version using simplified version of Boyer and Moore
  419.  *    Software-Practice and Experience, vol 10, 501-506 (1980)
  420.  */
  421. int PASCAL NEAR scanner(direct, beg_or_end, repeats)
  422. int    direct;        /* which way to go.*/
  423. int    beg_or_end;    /* put point at beginning or end of pattern.*/
  424. int    repeats;    /* search repetitions.*/
  425. {
  426.     register int    c;        /* character at current position */
  427.     register char    *patptr;    /* pointer into pattern */
  428.     char    *patrn;            /* string to scan for */
  429.     LINE    *curline;        /* current line during scan */
  430.     int    curoff;            /* position within current line */
  431.     LINE    *scanline;        /* current line during scanning */
  432.     int    scanoff;        /* position in scanned line */
  433.     int    jump;            /* next offset */
  434.  
  435.     /* If we are going in reverse, then the 'end' is actually
  436.      * the beginning of the pattern.  Toggle it.
  437.      */
  438.     beg_or_end ^= direct;
  439.  
  440.     /* Another directional problem: if we are searching
  441.      * forwards, use the pattern in pat[], and the jump
  442.      * size in lastchfjump.  Otherwise, use the reversed
  443.      * pattern in tap[], and the jump size of lastchbjump.
  444.      */
  445.     if (direct == FORWARD) {
  446.         patrn = &pat[0];
  447.         jump = lastchfjump;
  448.     }
  449.     else
  450.     {
  451.         patrn = &tap[0];
  452.         jump = lastchbjump;
  453.     }
  454.  
  455.     /* Set up local pointers to global ".".
  456.      */
  457.     curline = curwp->w_dotp;
  458.     curoff = curwp->w_doto;
  459.  
  460.     /* Scan each character until we hit the head link record.
  461.      * Get the character resolving newlines, offset
  462.      * by the pattern length, i.e. the last character of the
  463.      * potential match.
  464.      */
  465.     if (!fbound(patlenadd, &curline, &curoff, direct)) {
  466.         do
  467.         {
  468.             /* Set up the scanning pointers, and save
  469.              * the current position in case we match
  470.              * the search string at this point.
  471.              */
  472.             scanline = matchline = curline;
  473.             scanoff  = matchoff  = curoff;
  474.  
  475.             patptr = patrn;
  476.  
  477.             /* Scan through the pattern for a match.
  478.              */
  479.             while ((c = *patptr++) != '\0')
  480.                 if (!eq(c, nextch(&scanline, &scanoff, direct)))
  481.                     goto fail;
  482.  
  483.             /* A SUCCESSFULL MATCH!!!
  484.              * Flag that we have moved and reset the
  485.              * global "." pointers.
  486.              */
  487.             curwp->w_flag |= WFMOVE;
  488.             if (beg_or_end == PTEND)    /* at end of string */
  489.             {
  490.                 curwp->w_dotp = scanline;
  491.                 curwp->w_doto = scanoff;
  492.             }
  493.             else        /* at beginning of string */
  494.             {
  495.                 curwp->w_dotp = matchline;
  496.                 curwp->w_doto = matchoff;
  497.             }
  498.  
  499.             /* If we're heading in reverse, set the "match"
  500.              * pointers to the start of the string, for savematch().
  501.              */
  502.             if (direct == REVERSE) {
  503.                 matchline = scanline;
  504.                 matchoff = scanoff;
  505.             }
  506.  
  507.             if (savematch() == ABORT)
  508.                 return ABORT;
  509.  
  510.             /* Continue scanning if we haven't found
  511.              * the nth match.
  512.              */
  513.             if (--repeats <= 0)
  514.                 return (TRUE);
  515.             else
  516.             {
  517.                 curline = scanline;
  518.                 curoff = scanoff;
  519.             }
  520.  
  521. fail:;            /* continue to search */
  522.         } while (!fbound(jump, &curline, &curoff, direct));
  523.     }
  524.  
  525.     return FALSE;    /* We could not find a match */
  526. }
  527.  
  528. /*
  529.  * fbound -- Return information depending on whether we have hit a boundry
  530.  *    (and may therefore search no further) or if a trailing character
  531.  *    of the search string has been found.  See boundry() for search
  532.  *    restrictions.
  533.  */
  534. int PASCAL NEAR    fbound(jump, pcurline, pcuroff, dir)
  535. LINE    **pcurline;
  536. int    *pcuroff, dir, jump;
  537. {
  538.     register int    spare, curoff;
  539.     register LINE    *curline;
  540.  
  541.     curline = *pcurline;
  542.     curoff = *pcuroff;
  543.  
  544.     if (dir == FORWARD)
  545.     {
  546.         while (jump != 0) {
  547. #if     WINDOW_MSWIN
  548.                 {
  549.                 static int  o = 0;
  550.                 if (--o < 0) {
  551.                 longop(TRUE);
  552.                 o = 100;   /* to lower overhead, only 1/100
  553.                            calls to longop */
  554.                 }
  555.             }
  556. #endif
  557.             curoff += jump;
  558.             spare = curoff - llength(curline);
  559.  
  560.             if (curline == curbp->b_linep)
  561.                 return (TRUE);    /* hit end of buffer */
  562.  
  563.             while (spare > 0) {
  564.                 curline = lforw(curline);/* skip to next line */
  565.                 curoff = spare - 1;
  566.                 spare = curoff - llength(curline);
  567.                 if (curline == curbp->b_linep)
  568.                     return (TRUE);    /* hit end of buffer */
  569.             }
  570.  
  571.             if (spare == 0)
  572.                 jump = deltaf[(int) '\r'];
  573.             else
  574.                 jump = deltaf[(int) lgetc(curline, curoff)];
  575.         }
  576.  
  577.         /* The last character matches, so back up to start
  578.          * of possible match.
  579.          */
  580.         curoff -= patlenadd;
  581.  
  582.         while (curoff < 0) {
  583.             curline = lback(curline);    /* skip back a line */
  584.             curoff += llength(curline) + 1;
  585.         }
  586.  
  587.     }
  588.     else            /* Reverse.*/
  589.     {
  590.         jump++;        /* allow for offset in reverse */
  591.         while (jump != 0) {
  592. #if     WINDOW_MSWIN
  593.                 {
  594.                 static int  o = 0;
  595.                 if (--o < 0) {
  596.                 longop(TRUE);
  597.                 o = 100;   /* to lower overhead, only 1/100
  598.                            calls to longop */
  599.                 }
  600.             }
  601. #endif
  602.             curoff -= jump;
  603.             while (curoff < 0) {
  604.                 curline = lback(curline);    /* skip back a line */
  605.                 curoff += llength(curline) + 1;
  606.                 if (curline == curbp->b_linep)
  607.                     return (TRUE);    /* hit end of buffer */
  608.             }
  609.  
  610.             if (curoff == llength(curline))
  611.                 jump = deltab[(int) '\r'];
  612.             else
  613.                 jump = deltab[(int) lgetc(curline, curoff)];
  614.         }
  615.  
  616.         /* The last character matches, so back up to start
  617.          * of possible match.
  618.          */
  619.         curoff += patlenadd + 1;
  620.         spare = curoff - llength(curline);
  621.         while (spare > 0) {
  622.             curline = lforw(curline);    /* skip back a line */
  623.             curoff = spare - 1;
  624.             spare = curoff - llength(curline);
  625.         }
  626.     }
  627.  
  628.     *pcurline = curline;
  629.     *pcuroff = curoff;
  630.     return FALSE;
  631. }
  632.  
  633. /*
  634.  * setjtable -- Settting up search delta forward and delta backward
  635.  *    tables.  The reverse search string and string lengths are
  636.  *    set here, for table initialization and for substitution
  637.  *    purposes.  The default for any character to jump is the
  638.  *    pattern length.
  639.  */
  640. VOID PASCAL NEAR setjtable()
  641. {
  642.     int    i;
  643.  
  644.     rvstrcpy(tap, pat);
  645.     patlenadd = (matchlen = strlen(pat)) - 1;
  646.  
  647.     for (i = 0; i < HICHAR; i++) {
  648.         deltaf[i] = matchlen;
  649.         deltab[i] = matchlen;
  650.     }
  651.  
  652.     /* Now put in the characters contained
  653.      * in the pattern, duplicating the CASE.
  654.      */
  655.     for (i = 0; i < patlenadd; i++) {
  656.         if (isletter(pat[i]))
  657.             deltaf[(unsigned int) chcase((unsigned int) pat[i])]
  658.              = patlenadd - i;
  659.         deltaf[(unsigned int) pat[i]] = patlenadd - i;
  660.  
  661.         if (isletter(tap[i]))
  662.             deltab[(unsigned int) chcase((unsigned int) tap[i])]
  663.              = patlenadd - i;
  664.         deltab[(unsigned int) tap[i]] = patlenadd - i;
  665.     }
  666.  
  667.     /* The last character will have the pattern length
  668.      * unless there are duplicates of it.  Get the number to
  669.      * jump from the arrays delta, and overwrite with zeroes
  670.      * in delta duplicating the CASE.
  671.      *
  672.      * The last character is, of course, the first character
  673.      * of the reversed string.
  674.      */
  675.     lastchfjump = patlenadd + deltaf[(unsigned int) tap[0]];
  676.     lastchbjump = patlenadd + deltab[(unsigned int) pat[0]];
  677.  
  678.     if (isletter(tap[0]))
  679.         deltaf[(unsigned int) chcase(tap[0])] = 0;
  680.     deltaf[(unsigned int) tap[0]] = 0;
  681.  
  682.     if (isletter(pat[0]))
  683.         deltab[(unsigned int) chcase(pat[0])] = 0;
  684.     deltab[(unsigned int) pat[0]] = 0;
  685. }
  686.  
  687. /*
  688.  * rvstrcpy -- Reverse string copy.
  689.  */
  690. VOID PASCAL NEAR rvstrcpy(rvstr, str)
  691. register char    *rvstr, *str;
  692. {
  693.     register int i;
  694.  
  695.     str += (i = strlen(str));
  696.  
  697.     while (i-- > 0)
  698.         *rvstr++ = *--str;
  699.  
  700.     *rvstr = '\0';
  701. }
  702.  
  703. /*
  704.  * eq -- Compare two characters.  The "bc" comes from the buffer, "pc"
  705.  *    from the pattern.  If we are not in EXACT mode, fold out the case.
  706.  */
  707. int PASCAL NEAR eq(bc, pc)
  708. register int    bc;
  709. register int    pc;
  710. {
  711.     if ((curwp->w_bufp->b_mode & MDEXACT) == 0) {
  712.         if (islower(bc))
  713.             bc = chcase(bc);
  714.  
  715.         if (islower(pc))
  716.             pc = chcase(pc);
  717.     }
  718.  
  719.     return (bc == pc);
  720. }
  721.  
  722. /*
  723.  * readpattern -- Read a pattern.  Stash it in apat.  If it is the
  724.  *    search string (which means that the global variable pat[]
  725.  *    has been passed in), create the reverse pattern and the magic
  726.  *    pattern, assuming we are in MAGIC mode (and #defined that way).
  727.  *
  728.  *    Apat is not updated if the user types in an empty line.  If
  729.  *    the user typed an empty line, and there is no old pattern, it is
  730.  *    an error.  Display the old pattern, in the style of Jeff Lomicka.
  731.  *    There is some do-it-yourself control expansion.  Change to using
  732.  *    <META> to delimit the end-of-pattern to allow <NL>s in the search
  733.  *    string. 
  734.  */
  735. int PASCAL NEAR readpattern(prompt, apat, srch)
  736. char    *prompt;
  737. char    apat[];
  738. int    srch;
  739. {
  740.     register int    status;
  741.     char        tpat[NPAT+20];
  742.  
  743.     strcpy(tpat, prompt);    /* copy prompt to output string */
  744.     strcat(tpat, " [");
  745.     expandp(&apat[0], &tpat[strlen(tpat)], NPAT/2);    /* add old pattern */
  746.     strcat(tpat, "]");
  747.  
  748.     /* display the proper current search terminator character */
  749.     switch (sterm) {
  750.  
  751.         case CTRL | '[':    strcat(tpat, "<META>"); break;
  752.         case CTRL | 'M':    strcat(tpat, "<NL>"); break;
  753.         default:        strcat(tpat, "<");
  754.                     cmdstr(sterm, &(tpat[strlen(tpat)]));
  755.                     strcat(tpat, ">");
  756.     }
  757.     strcat(tpat, ": ");
  758.  
  759.     /* Read a pattern.  Either we get one,
  760.      * or we just get the META charater, and use the previous pattern.
  761.      * Then, if it's the search string, create the delta tables.
  762.      * *Then*, make the meta-pattern, if we are defined that way.
  763.      */
  764.     if ((status = mltreply(tpat, tpat, NPAT, sterm)) == TRUE) {
  765.         lastflag &= ~CFSRCH;
  766.         strcpy(apat, tpat);
  767.  
  768.         if (srch)
  769.             setjtable();
  770.  
  771.     }
  772.     else if (status == FALSE && apat[0] != 0)    /* Old one */
  773.         status = TRUE;
  774.  
  775. #if    MAGIC
  776.     /* Only make the meta-pattern if in magic mode, since the
  777.      * pattern in question might have an invalid meta combination.
  778.      */
  779.     if (status == TRUE)
  780.         if ((curwp->w_bufp->b_mode & MDMAGIC) == 0) {
  781.             mcclear();
  782.             rmcclear();
  783.         }
  784.         else
  785.             status = srch? mcstr(): rmcstr();
  786. #endif
  787.     return (status);
  788. }
  789.  
  790. /*
  791.  * savematch -- We found the pattern?  Let's save it away.
  792.  */
  793. int PASCAL NEAR savematch()
  794. {
  795.     register int    j;
  796.     REGION        tmpreg;
  797.  
  798.     if (patmatch != NULL)
  799.         free(patmatch);
  800.  
  801.     if ((patmatch = malloc(matchlen + 1)) == NULL) {
  802.         mlabort(TEXT94);
  803. /*            "%%Out of memory" */
  804.         return ABORT;
  805.     }
  806.  
  807.     tmpreg.r_offset = matchoff;
  808.     tmpreg.r_linep = matchline;
  809.     tmpreg.r_size = matchlen;
  810.  
  811. #if    MAGIC == 0
  812.     regtostr(patmatch, &tmpreg);
  813. #else
  814.     /*
  815.      * Save the groups.
  816.      */
  817.     grpmatch[0] = regtostr(patmatch, &tmpreg);
  818.     for (j = 1; j <= group_count; j++)
  819.     {
  820.         group_reg[j].r_size += group_len[j];
  821.  
  822.         if (grpmatch[j] != NULL)
  823.             free(grpmatch[j]);
  824.  
  825.         if ((grpmatch[j] = malloc(group_reg[j].r_size + 1)) != NULL)
  826.             regtostr(grpmatch[j], &group_reg[j]);
  827.         else {
  828.             mlabort(TEXT94);
  829. /*            "%%Out of memory" */
  830.             return ABORT;
  831.         }
  832.     }
  833. #endif
  834.     return TRUE;
  835. }
  836.  
  837. /*
  838.  * sreplace -- Search and replace.
  839.  */
  840. int PASCAL NEAR sreplace(f, n)
  841. int f;        /* default flag */
  842. int n;        /* # of repetitions wanted */
  843. {
  844.     return (replaces(FALSE, f, n));
  845. }
  846.  
  847. /*
  848.  * qreplace -- search and replace with query.
  849.  */
  850. int PASCAL NEAR qreplace(f, n)
  851. int f;        /* default flag */
  852. int n;        /* # of repetitions wanted */
  853. {
  854.     return (replaces(TRUE, f, n));
  855. }
  856.  
  857. /*
  858.  * replaces -- Search for a string and replace it with another
  859.  *    string.  Query might be enabled (according to kind).
  860.  */
  861. int PASCAL NEAR    replaces(kind, f, n)
  862. int    kind;    /* Query enabled flag */
  863. int    f;    /* default flag */
  864. int    n;    /* # of repetitions wanted */
  865. {
  866.     register int status;    /* success flag on pattern inputs */
  867.     register int nummatch;    /* number of found matches */
  868.     int numsub;        /* number of substitutions */
  869.     int nlflag;        /* last char of search string a <NL>? */
  870.     int nlrepl;        /* was a replace done on the last line? */
  871.     char c;            /* input char for query */
  872.     char tpat[NPAT];    /* temporary to hold search pattern */
  873.     LINE *origline;        /* original "." position */
  874.     int origoff;        /* and offset (for . query option) */
  875.     LINE *lastline;        /* position of last replace and */
  876.     int lastoff;        /* offset (for 'u' query option) */
  877.     int oldmatchlen;    /* Closure may alter the match length.*/
  878.     static char *oldpatmatch = NULL;    /* allocated memory for un-do.*/
  879.  
  880.     if (curbp->b_mode & MDVIEW)    /* don't allow this command if    */
  881.         return (rdonly());    /* we are in read only mode    */
  882.  
  883.     /* Check for negative repetitions.
  884.      */
  885.     if (f && n < 0)
  886.         return FALSE;
  887.  
  888.     /* Ask the user for the text of a pattern.
  889.      */
  890.     if ((status = readpattern(
  891.         (kind == FALSE ? TEXT84 : TEXT85), &pat[0], TRUE)) != TRUE)
  892. /*                "Replace" */
  893. /*                    "Query replace" */
  894.         return (status);
  895.  
  896.     /* Ask for the replacement string, and get its length.
  897.      */
  898.     if ((status = readpattern(TEXT86, &rpat[0], FALSE)) == ABORT)
  899. /*                "with" */
  900.         return (status);
  901.  
  902.     /* Set up flags so we can make sure not to do a recursive
  903.      * replace on the last line.
  904.      */
  905.     nlflag = (pat[matchlen - 1] == '\r');
  906.     nlrepl = FALSE;
  907.  
  908.     if (kind) {
  909.         /* Build query replace question string.
  910.          */
  911.         strcpy(tpat, TEXT87);
  912. /*                "Replace '" */
  913.         expandp(&pat[0], &tpat[strlen(tpat)], NPAT/3);
  914.         strcat(tpat, TEXT88);
  915. /*                "' with '" */
  916.         expandp(&rpat[0], &tpat[strlen(tpat)], NPAT/3);
  917.         strcat(tpat, "'? ");
  918.  
  919.         /* Initialize last replaced pointers.
  920.          */
  921.         lastline = NULL;
  922.         lastoff = 0;
  923.     }
  924.  
  925.     /* Save original . position, reset the number of matches and
  926.      * substitutions, and scan through the file.
  927.      */
  928.     origline = curwp->w_dotp;
  929.     origoff = curwp->w_doto;
  930.     numsub = 0;
  931.     nummatch = 0;
  932.  
  933.     while ( (f == FALSE || n > nummatch) &&
  934.         (nlflag == FALSE || nlrepl == FALSE) ) {
  935.         /* Search for the pattern.
  936.          * If we search with a regular expression,
  937.          * matchlen is reset to the true length of
  938.          * the matched string.
  939.          */
  940. #if    MAGIC
  941.         if (magical && (curwp->w_bufp->b_mode & MDMAGIC)) {
  942.             if (!mcscanner(FORWARD, PTBEG, 1))
  943.                 break;
  944.         }
  945.         else
  946. #endif
  947.             if (!scanner(FORWARD, PTBEG, 1))
  948.                 break;        /* all done */
  949.  
  950.         ++nummatch;    /* Increment # of matches */
  951.  
  952.         /* Check if we are on the last line.
  953.          */
  954.         nlrepl = (lforw(curwp->w_dotp) == curwp->w_bufp->b_linep);
  955.  
  956.         /* Check for query.
  957.          */
  958.         if (kind) {
  959.             /* Get the query.
  960.              */
  961. pprompt:        mlwrite(&tpat[0]);
  962. qprompt:
  963.             update(TRUE);  /* show the proposed place to change */
  964.             c = tgetc();            /* and input */
  965.             mlerase();            /* and clear it */
  966.  
  967.             /* And respond appropriately.
  968.              */
  969.             switch (c) {
  970. #if    FRENCH
  971.                 case 'o':    /* oui, substitute */
  972.                 case 'O':
  973. #endif
  974.                 case 'y':    /* yes, substitute */
  975.                 case 'Y':
  976.                 case ' ':
  977.                     break;
  978.  
  979.                 case 'n':    /* no, onward */
  980.                 case 'N':
  981.                     forwchar(FALSE, 1);
  982.                     continue;
  983.  
  984.                 case '!':    /* yes/stop asking */
  985.                     kind = FALSE;
  986.                     break;
  987.  
  988.                 case 'u':    /* undo last and re-prompt */
  989.                 case 'U':
  990.                     /* Restore old position.
  991.                      */
  992.                     if (lastline == NULL) {
  993.                         /* There is nothing to undo.
  994.                          */
  995.                         TTbeep();
  996.                         goto pprompt;
  997.                     }
  998.                     curwp->w_dotp = lastline;
  999.                     curwp->w_doto = lastoff;
  1000.                     lastline = NULL;
  1001.                     lastoff = 0;
  1002.  
  1003.                     /* Delete the new string,
  1004.                      * restore the old match.
  1005.                      */
  1006.                     backchar(FALSE, replen);
  1007.                     status = delins(replen, oldpatmatch, FALSE);
  1008.                     if (status != TRUE)
  1009.                         return (status);
  1010.  
  1011.                     /* Record one less substitution,
  1012.                      * backup, save our place, and
  1013.                      * reprompt.
  1014.                      */
  1015.                     --numsub;
  1016.                     backchar(FALSE, oldmatchlen);
  1017.                     matchlen = oldmatchlen;
  1018.                     matchline = curwp->w_dotp;
  1019.                     matchoff  = curwp->w_doto;
  1020.                     continue;
  1021.  
  1022.                 case '.':    /* abort! and return */
  1023.                     /* restore old position */
  1024.                     curwp->w_dotp = origline;
  1025.                     curwp->w_doto = origoff;
  1026.                     curwp->w_flag |= WFMOVE;
  1027.  
  1028.                 case BELL:    /* abort! and stay */
  1029.                     mlwrite(TEXT89);
  1030. /*                        "Aborted!" */
  1031.                     return FALSE;
  1032.  
  1033.                 default:    /* bitch and beep */
  1034.                     TTbeep();
  1035.  
  1036.                 case '?':    /* help me */
  1037.                     mlwrite(TEXT90);
  1038. /*"(Y)es, (N)o, (!)Do rest, (U)ndo last, (^G)Abort, (.)Abort back, (?)Help: "*/
  1039.                     goto qprompt;
  1040.  
  1041.             }    /* end of switch */
  1042.         }    /* end of "if kind" */
  1043.  
  1044.         /* if this is the point origin, flag so we a can reset it */
  1045.         if (curwp->w_dotp == origline) {
  1046.             origline = NULL;
  1047.             lastline = lback(curwp->w_dotp);
  1048.         }
  1049.  
  1050.         /* Delete the sucker, and insert its
  1051.          * replacement.
  1052.          */
  1053. #if    MAGIC
  1054.         status = delins(matchlen, &rpat[0], rmagical);
  1055. #else
  1056.         status = delins(matchlen, &rpat[0], FALSE);
  1057. #endif
  1058.         if (origline == NULL) {
  1059.             origline = lforw(lastline);
  1060.             origoff = 0;
  1061.         }
  1062.  
  1063.         if (status != TRUE)
  1064.             return (status);
  1065.  
  1066.         numsub++;    /* increment # of substitutions */
  1067.  
  1068.         /* Save our position, the match length, and the string
  1069.          * matched if we are query-replacing, as we may undo
  1070.          * the replacement. If we are not querying, check to
  1071.          * make sure that we didn't replace an empty string
  1072.          * (possible in MAGIC mode), because we'll infinite loop.
  1073.          */
  1074.         if (kind) {
  1075.             lastline = curwp->w_dotp;
  1076.             lastoff = curwp->w_doto;
  1077.             oldmatchlen = matchlen;    /* Save the length for un-do.*/
  1078.  
  1079.             if (oldpatmatch != NULL)
  1080.                 free(oldpatmatch);
  1081.  
  1082.             if ((oldpatmatch = malloc(matchlen + 1)) == NULL) {
  1083.                 mlabort(TEXT94);
  1084. /*                    "%%Out of memory" */
  1085.                 return ABORT;
  1086.             }
  1087.             strcpy(oldpatmatch, patmatch);
  1088.         }
  1089.         else if (matchlen == 0) {
  1090.             mlwrite(TEXT91);
  1091. /*                "Empty string replaced, stopping." */
  1092.             return FALSE;
  1093.         }
  1094.     }
  1095.  
  1096.     /* And report the results.
  1097.      */
  1098.     mlwrite(TEXT92, numsub);
  1099. /*        "%d substitutions" */
  1100.     return (TRUE);
  1101. }
  1102.  
  1103. /*
  1104.  * delins -- Delete a specified length from the current point
  1105.  *    then either insert the string directly, or make use of
  1106.  *    replacement meta-array.
  1107.  */
  1108. int PASCAL NEAR delins(dlength, instr, use_rmc)
  1109. int    dlength;
  1110. char    *instr;
  1111. int    use_rmc;
  1112. {
  1113.     register int    status;
  1114.     register char    *rstr;
  1115. #if    MAGIC
  1116.     register RMC    *rmcptr;
  1117. #endif
  1118.  
  1119.     replen = 0;
  1120.  
  1121.     /* Zap what we gotta,
  1122.      * and insert its replacement.
  1123.      */
  1124.     if ((status = ldelete((long) dlength, FALSE)) != TRUE)
  1125.         mlwrite(TEXT93);
  1126. /*            "%%ERROR while deleting" */
  1127.     else
  1128. #if    MAGIC
  1129.         if (use_rmc && (curwp->w_bufp->b_mode & MDMAGIC)) {
  1130.             rmcptr = &rmcpat[0];
  1131.             while (rmcptr->mc_type != MCNIL && status == TRUE) {
  1132.                 if (rmcptr->mc_type == LITCHAR)
  1133.                     status = linstr(rstr = rmcptr->u.rstr);
  1134.                 else if (rmcptr->mc_type == DITTO)
  1135.                     status = linstr(rstr = patmatch);
  1136.                 else
  1137.                     status = linstr(rstr = fixnull(grpmatch[rmcptr->u.group_no]));
  1138.                 replen += strlen(rstr);
  1139.                 rmcptr++;
  1140.             }
  1141.         }
  1142.         else
  1143. #endif
  1144.         {
  1145.             status = linstr(instr);
  1146.             replen = strlen(instr);
  1147.         }
  1148.  
  1149.     return (status);
  1150. }
  1151.  
  1152. /*
  1153.  * expandp -- Expand control key sequences for output.
  1154.  */
  1155. int PASCAL NEAR expandp(srcstr, deststr, maxlength)
  1156. char    *srcstr;    /* string to expand */
  1157. char    *deststr;    /* destination of expanded string */
  1158. int    maxlength;    /* maximum chars in destination */
  1159. {
  1160.     unsigned char c;    /* current char to translate */
  1161.  
  1162.     /* Scan through the string.
  1163.      */
  1164.     while ((c = *srcstr++) != 0) {
  1165.         if (c == '\r')        /* it's a newline */
  1166.         {
  1167.             *deststr++ = '<';
  1168.             *deststr++ = 'N';
  1169.             *deststr++ = 'L';
  1170.             *deststr++ = '>';
  1171.             maxlength -= 4;
  1172.         }
  1173.         else if (c < 0x20 || c == 0x7f)    /* control character */
  1174.         {
  1175.             *deststr++ = '^';
  1176.             *deststr++ = c ^ 0x40;
  1177.             maxlength -= 2;
  1178.         }
  1179.         else if (c == '%') {
  1180.             *deststr++ = '%';
  1181.             *deststr++ = '%';
  1182.             maxlength -= 2;
  1183.         }
  1184.         else            /* any other character */
  1185.         {
  1186.             *deststr++ = c;
  1187.             maxlength--;
  1188.         }
  1189.  
  1190.         /* check for maxlength */
  1191.         if (maxlength < 4) {
  1192.             *deststr++ = '$';
  1193.             *deststr = '\0';
  1194.             return FALSE;
  1195.         }
  1196.     }
  1197.     *deststr = '\0';
  1198.     return (TRUE);
  1199. }
  1200.  
  1201. /*
  1202.  * boundry -- Return information depending on whether we may search no
  1203.  *    further.  Beginning of file and end of file are the obvious
  1204.  *    cases, but we may want to add further optional boundry restrictions
  1205.  *    in future, a' la VMS EDT.  At the moment, just return (TRUE) or
  1206.  *    FALSE depending on if a boundry is hit (ouch).
  1207.  */
  1208. int PASCAL NEAR boundry(curline, curoff, dir)
  1209. LINE    *curline;
  1210. int    curoff, dir;
  1211. {
  1212.     register int    border;
  1213.  
  1214.     if (dir == FORWARD) {
  1215.         border = (curoff == llength(curline)) &&
  1216.              (lforw(curline) == curbp->b_linep);
  1217.     }
  1218.     else
  1219.     {
  1220.         border = (curoff == 0) &&
  1221.              (lback(curline) == curbp->b_linep);
  1222.     }
  1223.     return (border);
  1224. }
  1225.  
  1226. /*
  1227.  * nextch -- retrieve the next/previous character in the buffer,
  1228.  *    and advance/retreat the point.
  1229.  *    The order in which this is done is significant, and depends
  1230.  *    upon the direction of the search.  Forward searches look at
  1231.  *    the current character and move, reverse searches move and
  1232.  *    look at the character.
  1233.  */
  1234. int PASCAL NEAR nextch(pcurline, pcuroff, dir)
  1235. LINE    **pcurline;
  1236. int    *pcuroff;
  1237. int    dir;
  1238. {
  1239.     register LINE    *curline;
  1240.     register int    curoff;
  1241.     register int    c;
  1242.  
  1243.     curline = *pcurline;
  1244.     curoff = *pcuroff;
  1245.  
  1246.     if (dir == FORWARD) {
  1247.         if (curoff == llength(curline))        /* if at EOL */
  1248.         {
  1249.             curline = lforw(curline);    /* skip to next line */
  1250.             curoff = 0;
  1251.             c = '\r';            /* and return a <NL> */
  1252.         }
  1253.         else
  1254.             c = lgetc(curline, curoff++);    /* get the char */
  1255.     }
  1256.     else            /* Reverse.*/
  1257.     {
  1258.         if (curoff == 0) {
  1259.             curline = lback(curline);
  1260.             curoff = llength(curline);
  1261.             c = '\r';
  1262.         }
  1263.         else
  1264.             c = lgetc(curline, --curoff);
  1265.  
  1266.     }
  1267.     *pcurline = curline;
  1268.     *pcuroff = curoff;
  1269.  
  1270.     return (c);
  1271. }
  1272.  
  1273. #if    MAGIC
  1274. /*
  1275.  * mcstr -- Set up the 'magic' array.  The closure symbol is taken as
  1276.  *    a literal character when (1) it is the first character in the
  1277.  *    pattern, and (2) when preceded by a symbol that does not allow
  1278.  *    closure, such as beginning or end of line symbol, or another
  1279.  *    closure symbol.
  1280.  *
  1281.  *    Coding comment (jmg):  yes, i know i have gotos that are, strictly
  1282.  *    speaking, unnecessary.  But right now we are so cramped for
  1283.  *    code space that i will grab what i can in order to remain
  1284.  *    within the 64K limit.  C compilers actually do very little
  1285.  *    in the way of optimizing - they expect you to do that.
  1286.  */
  1287. int PASCAL NEAR mcstr()
  1288. {
  1289.     MC    *mcptr, *rtpcm;
  1290.     char    *patptr;
  1291.     int    pchr;
  1292.     int    status = TRUE;
  1293.     int    does_closure = FALSE;
  1294.     int    mj = 0;
  1295.     int    group_stack[MAXGROUPS];
  1296.     int    stacklevel = 0;
  1297.  
  1298.     /* If we had metacharacters in the MC array previously,
  1299.      * free up any bitmaps that may have been allocated, and
  1300.      * reset magical.
  1301.      */
  1302.     if (magical)
  1303.         mcclear();
  1304.  
  1305.     mcptr = &mcpat[0];
  1306.     patptr = &pat[0];
  1307.  
  1308.     while ((pchr = *patptr) && status) {
  1309.         switch (pchr) {
  1310.             case MC_CCL:
  1311.                 status = cclmake(&patptr, mcptr);
  1312.                 magical = TRUE;
  1313.                 does_closure = TRUE;
  1314.                 break;
  1315.  
  1316.             case MC_BOL:
  1317.                 /* If the BOL character isn't the
  1318.                  * first in the pattern, we assume
  1319.                  * it's a literal instead.
  1320.                  */
  1321.                 if (mj != 0)
  1322.                     goto litcase;
  1323.  
  1324.                 mcptr->mc_type = BOL;
  1325.                 magical = TRUE;
  1326.                 break;
  1327.  
  1328.             case MC_EOL:
  1329.                 /* If the EOL character isn't the
  1330.                  * last in the pattern, we assume
  1331.                  * it's a literal instead.
  1332.                  */
  1333.                 if (*(patptr + 1) != '\0')
  1334.                     goto litcase;
  1335.  
  1336.                 mcptr->mc_type = EOL;
  1337.                 magical = TRUE;
  1338.                 break;
  1339.  
  1340.             case MC_ANY:
  1341.                 mcptr->mc_type = ANY;
  1342.                 magical = TRUE;
  1343.                 does_closure = TRUE;
  1344.                 break;
  1345.  
  1346.             case MC_CLOSURE:
  1347.             case MC_CLOSURE_1:
  1348.             case MC_ZEROONE:
  1349.  
  1350.                 /* Does the closure symbol mean closure here?
  1351.                  * If so, back up to the previous element
  1352.                  * and indicate it is enclosed.
  1353.                  */
  1354.                 if (does_closure == FALSE)
  1355.                     goto litcase;
  1356.                 mj--;
  1357.                 mcptr--;
  1358.                 if (pchr == MC_CLOSURE)
  1359.                     mcptr->mc_type |= CLOSURE;
  1360.                 else if (pchr == MC_CLOSURE_1)
  1361.                     mcptr->mc_type |= CLOSURE_1;
  1362.                 else
  1363.                     mcptr->mc_type |= ZEROONE;
  1364.  
  1365.                 magical = TRUE;
  1366.                 does_closure = FALSE;
  1367.                 break;
  1368.  
  1369.             case MC_ESC:
  1370.  
  1371.                 /* No break between here and LITCHAR if
  1372.                  * the next character is to be taken literally.
  1373.                  */
  1374.                 magical = TRUE;
  1375.                 pchr = *++patptr;
  1376.                 if (pchr == MC_GRPBEG) {
  1377.                     /* Start of a group.  Indicate it, and
  1378.                      * set magical.
  1379.                      */
  1380.                     if (++group_count < MAXGROUPS) {
  1381.                         mcptr->mc_type = GRPBEG;
  1382.                         mcptr->u.group_no = group_count;
  1383.                         group_stack[stacklevel++] = group_count;
  1384.                         does_closure = FALSE;
  1385.                     }
  1386.                     else
  1387.                     {
  1388.                         mlwrite(TEXT221);
  1389. /*                            "Too many groups" */
  1390.                         status = FALSE;
  1391.                     }
  1392.                     break;
  1393.                 }
  1394.                 else if (pchr == MC_GRPEND) {
  1395.                     /* If we've no groups to close, assume
  1396.                      * a literal character.  Otherwise,
  1397.                      * indicate the end of a group.
  1398.                      */
  1399.                     if (stacklevel > 0) {
  1400.                         mcptr->u.group_no = group_stack[--stacklevel];
  1401.                         mcptr->mc_type = GRPEND;
  1402.                         does_closure = FALSE;
  1403.                         break;
  1404.                     }
  1405.                 }
  1406.                 else if (pchr == '\0') {
  1407.                     pchr = '\\';
  1408.                     --patptr;
  1409.                 }
  1410.             default:
  1411. litcase:            mcptr->mc_type = LITCHAR;
  1412.                 mcptr->u.lchar = pchr;
  1413.                 does_closure = TRUE;
  1414.                 break;
  1415.         }        /* End of switch.*/
  1416.         mcptr++;
  1417.         patptr++;
  1418.         mj++;
  1419.     }        /* End of while.*/
  1420.  
  1421.     /* Close off the meta-string, then set up the reverse array,
  1422.      * if the status is good.
  1423.      *
  1424.      * The beginning and end of groups are reversed by flipping
  1425.      * the bits, if they are set.
  1426.      *
  1427.      * If the status is not good, nil out the meta-pattern.
  1428.      * Even if the status is bad from the cclmake() routine,
  1429.      * the bitmap for that member is guaranteed to be freed.
  1430.      * So we stomp a MCNIL value there, and call mcclear()
  1431.      * to free any other bitmaps.
  1432.      *
  1433.      * Please note the structure assignment - your compiler may
  1434.      * not like that.
  1435.      */
  1436.     mcptr->mc_type = MCNIL;
  1437.     if (stacklevel) {
  1438.         status = FALSE;
  1439.         mlwrite(TEXT222);
  1440. /*            "Group not ended"    */
  1441.     }
  1442.  
  1443.     if (status) {
  1444.         rtpcm = &tapcm[0];
  1445.         while (--mj >= 0) {
  1446. #if    LATTICE
  1447.             movmem(--mcptr, rtpcm, sizeof (MC));
  1448. #else
  1449.             *rtpcm = *--mcptr;
  1450. #endif
  1451.             rtpcm++;
  1452.         }
  1453.         rtpcm->mc_type = MCNIL;
  1454.     }
  1455.     else
  1456.     {
  1457.         (--mcptr)->mc_type = MCNIL;
  1458.         mcclear();
  1459.     }
  1460.  
  1461.     return (status);
  1462. }
  1463.  
  1464. /*
  1465.  * rmcstr -- Set up the replacement 'magic' array.  Note that if there
  1466.  *    are no meta-characters encountered in the replacement string,
  1467.  *    the array is never actually created - we will just use the
  1468.  *    character array rpat[] as the replacement string.
  1469.  */
  1470. int PASCAL NEAR rmcstr()
  1471. {
  1472.     RMC    *rmcptr;
  1473.     char    *patptr;
  1474.     int    pchr;
  1475.     int    status = TRUE;
  1476.     int    mj;
  1477.  
  1478.     patptr = &rpat[0];
  1479.     rmcptr = &rmcpat[0];
  1480.     mj = 0;
  1481.     rmagical = FALSE;
  1482.  
  1483.     while (*patptr && status == TRUE) {
  1484.         switch (*patptr) {
  1485.             case MC_DITTO:
  1486.  
  1487.                 /* If there were non-magical characters in
  1488.                  * the string before reaching this character
  1489.                  * plunk it in the replacement array before
  1490.                  * processing the current meta-character.
  1491.                  */
  1492.                 if (mj != 0) {
  1493.                     rmcptr->mc_type = LITCHAR;
  1494.                     if ((rmcptr->u.rstr = malloc(mj + 1)) == NULL) {
  1495.                         mlabort(TEXT94);
  1496. /*                            "%%Out of memory" */
  1497.                         status = FALSE;
  1498.                         break;
  1499.                     }
  1500.                     bytecopy(rmcptr->u.rstr, patptr - mj, mj);
  1501.                     rmcptr++;
  1502.                     mj = 0;
  1503.                 }
  1504.                 rmcptr->mc_type = DITTO;
  1505.                 rmcptr++;
  1506.                 rmagical = TRUE;
  1507.                 break;
  1508.  
  1509.             case MC_ESC:
  1510.                 pchr = *(patptr + 1);    /* peek at next char.*/
  1511.                 if (pchr <= '9' && pchr >= '1') {
  1512.                     if (mj != 0) {
  1513.                         rmcptr->mc_type = LITCHAR;
  1514.                         if ((rmcptr->u.rstr = malloc(mj + 1)) == NULL) {
  1515.                             mlabort(TEXT94);
  1516. /*                            "%%Out of memory" */
  1517.                             status = FALSE;
  1518.                             break;
  1519.                         }
  1520.                         bytecopy(rmcptr->u.rstr, patptr - mj, mj);
  1521.                         rmcptr++;
  1522.                         mj = 0;
  1523.                     }
  1524.                     rmcptr->mc_type = GROUP;
  1525.                     rmcptr->u.group_no = pchr - '0';
  1526.                     patptr++;
  1527.                 }
  1528.                 else
  1529.                 {
  1530.                     rmcptr->mc_type = LITCHAR;
  1531.  
  1532.                     /* We malloc mj plus two here, instead
  1533.                      * of one, because we have to count the
  1534.                      * current character.
  1535.                      */
  1536.                     if ((rmcptr->u.rstr = malloc(mj + 2)) == NULL) {
  1537.                         mlabort(TEXT94);
  1538. /*                        "%%Out of memory" */
  1539.                         status = FALSE;
  1540.                         break;
  1541.                     }
  1542.  
  1543.                     bytecopy(rmcptr->u.rstr, patptr - mj, mj + 1);
  1544.  
  1545.                     /* If MC_ESC is not the last character
  1546.                      * in the string, find out what it is
  1547.                      * escaping, and overwrite the last
  1548.                      * character with it.
  1549.                      */
  1550.                     if (pchr != '\0') {
  1551.                         *((rmcptr->u.rstr) + mj) = pchr;
  1552.                         patptr++;
  1553.                     }
  1554.                     mj = 0;
  1555.                 }
  1556.  
  1557.                 rmcptr++;
  1558.                 rmagical = TRUE;
  1559.                 break;
  1560.  
  1561.             default:
  1562.                 mj++;
  1563.         }
  1564.         patptr++;
  1565.     }
  1566.  
  1567.     if (rmagical && mj > 0) {
  1568.         rmcptr->mc_type = LITCHAR;
  1569.         if ((rmcptr->u.rstr = malloc(mj + 1)) == NULL) {
  1570.             mlabort(TEXT94);
  1571. /*                "%%Out of memory" */
  1572.             status = FALSE;
  1573.         }
  1574.         bytecopy(rmcptr->u.rstr, patptr - mj, mj);
  1575.         rmcptr++;
  1576.     }
  1577.  
  1578.     rmcptr->mc_type = MCNIL;
  1579.     return (status);
  1580. }
  1581.  
  1582. /*
  1583.  * mcclear -- Free up any CCL bitmaps, and MCNIL the MC search arrays.
  1584.  */
  1585. VOID PASCAL NEAR mcclear()
  1586. {
  1587.     register MC    *mcptr;
  1588.     register int    j;
  1589.  
  1590.     mcptr = &mcpat[0];
  1591.  
  1592.     while (mcptr->mc_type != MCNIL) {
  1593.         if ((mcptr->mc_type == CCL) || (mcptr->mc_type == NCCL))
  1594.             free(mcptr->u.cclmap);
  1595.         mcptr++;
  1596.     }
  1597.     mcpat[0].mc_type = tapcm[0].mc_type = MCNIL;
  1598.  
  1599.     /*
  1600.      * Remember that grpmatch[0] == patmatch.
  1601.      */
  1602.     for (j = 0; j < MAXGROUPS; j++) {
  1603.         if (grpmatch[j] != NULL) {
  1604.             free(grpmatch[j]);
  1605.             grpmatch[j] = NULL;
  1606.         }
  1607.     }
  1608.     patmatch = NULL;
  1609.     group_count = 0;
  1610.     magical = FALSE;
  1611. }
  1612.  
  1613. /*
  1614.  * rmcclear -- Free up any strings, and MCNIL the RMC array.
  1615.  */
  1616. VOID PASCAL NEAR rmcclear()
  1617. {
  1618.     register RMC    *rmcptr;
  1619.     register int    j;
  1620.  
  1621.     rmcptr = &rmcpat[0];
  1622.  
  1623.     while (rmcptr->mc_type != MCNIL) {
  1624.         if (rmcptr->mc_type == LITCHAR)
  1625.             free(rmcptr->u.rstr);
  1626.         rmcptr++;
  1627.     }
  1628.  
  1629.     rmcpat[0].mc_type = MCNIL;
  1630. }
  1631.  
  1632. /*
  1633.  * mceq -- meta-character equality with a character.  In Kernighan & Plauger's
  1634.  *    Software Tools, this is the function omatch(), but i felt there were
  1635.  *    too many functions with the 'match' name already.
  1636.  */
  1637. int PASCAL NEAR    mceq(bc, mt)
  1638. int    bc;
  1639. MC    *mt;
  1640. {
  1641.     register int result;
  1642.  
  1643.     switch (mt->mc_type & MASKCLO) {
  1644.         case LITCHAR:
  1645.             result = eq(bc, (int) mt->u.lchar);
  1646.             break;
  1647.  
  1648.         case ANY:
  1649.             result = (bc != '\r');
  1650.             break;
  1651.  
  1652.         case CCL:
  1653.             if (!(result = biteq(bc, mt->u.cclmap))) {
  1654.                 if ((curwp->w_bufp->b_mode & MDEXACT) == 0 &&
  1655.                     (isletter(bc))) {
  1656.                     result = biteq(chcase(bc), mt->u.cclmap);
  1657.                 }
  1658.             }
  1659.             break;
  1660.  
  1661.         case NCCL:
  1662.             result = !biteq(bc, mt->u.cclmap);
  1663.  
  1664.             if ((curwp->w_bufp->b_mode & MDEXACT) == 0 &&
  1665.                 (isletter(bc))) {
  1666.                 result &= !biteq(chcase(bc), mt->u.cclmap);
  1667.             }
  1668.             break;
  1669.  
  1670.         default:
  1671.             mlwrite(TEXT95, mt->mc_type);
  1672. /*                "%%mceq: what is %d?" */
  1673.             result = FALSE;
  1674.             break;
  1675.  
  1676.     }    /* End of switch.*/
  1677.  
  1678.     return (result);
  1679. }
  1680.  
  1681. /*
  1682.  * cclmake -- create the bitmap for the character class.
  1683.  *    ppatptr is left pointing to the end-of-character-class character,
  1684.  *    so that a loop may automatically increment with safety.
  1685.  */
  1686. int PASCAL NEAR    cclmake(ppatptr, mcptr)
  1687. char    **ppatptr;
  1688. MC    *mcptr;
  1689. {
  1690.     BITMAP        bmap;
  1691.     register char    *patptr;
  1692.     register int    pchr, ochr;
  1693.  
  1694. #if    OS2
  1695.     if ((bmap = (BITMAP) malloc(HIBYTE_)) == NULL) {
  1696.         mlwrite(TEXT94);
  1697. /*            "%%Out of memory" */
  1698.         return FALSE;
  1699.     }
  1700.  
  1701.     memset(bmap, 0, HIBYTE_);
  1702. #else
  1703.     if ((bmap = (BITMAP) malloc(HIBYTE)) == NULL) {
  1704.         mlabort(TEXT94);
  1705. /*            "%%Out of memory" */
  1706.         return FALSE;
  1707.     }
  1708.  
  1709.     memset(bmap, 0, HIBYTE);
  1710. #endif
  1711.  
  1712.     mcptr->u.cclmap = bmap;
  1713.     patptr = *ppatptr;
  1714.     ochr = MC_CCL;
  1715.  
  1716.     /*
  1717.      * Test the initial character(s) in ccl for
  1718.      * special cases - negate ccl, or an end ccl
  1719.      * character as a first character.  Anything
  1720.      * else gets set in the bitmap.
  1721.      */
  1722.     if (*++patptr == MC_NCCL) {
  1723.         patptr++;
  1724.         mcptr->mc_type = NCCL;
  1725.     }
  1726.     else
  1727.         mcptr->mc_type = CCL;
  1728.  
  1729.     if ((pchr = *patptr) == MC_ECCL) {
  1730.         mlwrite(TEXT96);
  1731. /*            "%%No characters in character class" */
  1732.         free(bmap);
  1733.         return FALSE;
  1734.     }
  1735.  
  1736.     while (pchr != MC_ECCL && pchr != '\0') {
  1737.         switch (pchr) {
  1738.             /* Range character loses its meaning if it is
  1739.              * the first or last character in the class.  We
  1740.              * also treat it as un-ordinary if the order is
  1741.              * wrong, e.g. "z-a".
  1742.              */
  1743.             case MC_RCCL:
  1744.                 pchr = *(patptr + 1);
  1745.                 if (ochr == MC_CCL || pchr == MC_ECCL ||
  1746.                     ochr > pchr)
  1747.                     setbit(MC_RCCL, bmap);
  1748.                 else
  1749.                 {
  1750.                     do {
  1751.                         setbit(++ochr, bmap);
  1752.                     } while (ochr < pchr);
  1753.                     patptr++;
  1754.                 }
  1755.                 break;
  1756.  
  1757.             /* Note: no break between case MC_ESC and the default.
  1758.              */
  1759.             case MC_ESC:
  1760.                 pchr = *++patptr;
  1761.             default:
  1762.                 setbit(pchr, bmap);
  1763.                 break;
  1764.         }
  1765.         ochr = pchr;
  1766.         pchr = *++patptr;
  1767.     }
  1768.  
  1769.     *ppatptr = patptr;
  1770.  
  1771.     if (pchr == '\0') {
  1772.         mlwrite(TEXT97);
  1773. /*            "%%Character class not ended" */
  1774.         free(bmap);
  1775.         return FALSE;
  1776.     }
  1777.     return TRUE;
  1778. }
  1779.  
  1780. /*
  1781.  * biteq -- is the character in the bitmap?
  1782.  */
  1783. int PASCAL NEAR    biteq(bc, cclmap)
  1784. int    bc;
  1785. BITMAP    cclmap;
  1786. {
  1787.     if (bc >= HICHAR)
  1788.         return FALSE;
  1789.  
  1790.     return ( (*(cclmap + (bc >> 3)) & BIT(bc & 7))? TRUE: FALSE );
  1791. }
  1792.  
  1793. /*
  1794.  * setbit -- Set a bit (ON only) in the bitmap.
  1795.  */
  1796. VOID PASCAL NEAR setbit(bc, cclmap)
  1797. int    bc;
  1798. BITMAP    cclmap;
  1799. {
  1800.     if (bc < HICHAR)
  1801.         *(cclmap + (bc >> 3)) |= BIT(bc & 7);
  1802. }
  1803. #endif
  1804.